'''
Created on 26/03/2009

@author: eh01
'''
from httplib import BadStatusLine, CannotSendRequest
from im.ds import dsconstants
from im.ds.dso import DSO
from im.ds.dsstub import DSStub
from im.ds.dst import DST
from im.event import eventconstants
from im.event.event import Event
from im.event.eventhandler import BaseEventHandler
from im.event.eventmanager import EventManager
from im.timerservice.timerservicemanager import TimerServiceManager
from socket import error, timeout
import base64
import datetime
import httplib

class SendToHubHandler(BaseEventHandler):
    '''
    Handles the responsibility of sending messages to the hub
    '''

    # http connection to hub
    __conn = None
    
    def __init__(self):
        '''
        Constructor
        '''
        # initially create a connection to hub
#        print 'initially set up a connection to send to hub'
        self.connect_to_hub()
    
    def __del__(self):
        self.disconnect_from_hub()

    def disconnect_from_hub(self):
#        print 'disconnect from hub'
        self.__conn.close()
        
    def connect_to_hub(self):
        """ 
        this creates a http connection based on the HTTP 1.1 Protocol.
        This by default creates a 'keep-alive' connection as we have not 
        specified connection = 'close'
        We are using connection = 'keep-alive' to reduce the response times
        of the hub, by not having to create a new TCP/IP connection each
        time we want to talk to the hub
        """
        # To test the timeout period, change the imhttpserver to be single threaded,
        # and then run a request (e.g like a http get with wait=true) and then this 
        # request afterwards, and if the first request hasn't been fulfilled, then this
        # request should time out        
        try:
            self.__conn = httplib.HTTPConnection(DSStub().get_hub_base_url(), timeout=2)
        except Exception, e:
#            print "can't connect"
            print e
        
    def handle_hub_error(self, message_id, error_code):
        ds = DSStub()
        (http_request, lock) = ds.get_outstanding_requests(message_id)

        dst1 = DST(subject=dsconstants.SUBJECT_ROOT, 
                   predicate=dsconstants.PREDICATE_HTTP_REQUEST, 
                   literal=http_request)
        dst2 = DST(subject=dsconstants.SUBJECT_ROOT, 
                   predicate=dsconstants.PREDICATE_LOCK, 
                   literal=lock)
        dst3 = DST(subject=dsconstants.SUBJECT_ROOT, 
                   predicate=dsconstants.PREDICATE_ERROR_CODE, 
                   literal=error_code)
        new_dso = DSO(dsts=[dst1,dst2,dst3])
        event = Event(eventconstants.HUB_ERROR_EVENT_TYPE, new_dso)
        EventManager().add_event(event)                          
    
    def create_request_and_send(self, request_url, message, http_headers, message_id):
        
        self.__conn.request(method="PUT", url=request_url, body=message, headers=http_headers)
        response = self.__conn.getresponse()
        response.read()
#        print "Response code: " + str(response.status)
        if response.status != 302 and message_id:
            self.handle_hub_error(message_id, response.status)
        elif message_id:
            # if message was sent succesfully and there is a message_id,
            # create a timer event to handle a timeout situation
            request_id_dst = DST(subject=dsconstants.SUBJECT_ROOT, 
                      predicate=dsconstants.PREDICATE_REQUEST_ID, 
                      literal=message_id)
            dso = DSO(dsts=[request_id_dst])
            event = Event(eventconstants.RESPONSE_TIMEOUT_EVENT_TYPE, dso)
            ten_seconds = datetime.timedelta(seconds=10)
            timeout_timestamp = datetime.datetime.now() + ten_seconds 
            TimerServiceManager().add_timer_event(timeout_timestamp, event)      

    def send_http_request(self, request_url, message, http_headers, message_id):
        try:
            self.create_request_and_send(request_url, message, http_headers, message_id)
        except BadStatusLine, e:
            # Raised if a server responds with a HTTP status code that we don't understand
            print str(type(e)) + ": " + str(e) + ": reconnect"
            self.connect_to_hub()
            self.create_request_and_send(request_url, message, http_headers, message_id)
        except CannotSendRequest, e:
            # Raised if the hub is not started, so the node cannot make the connection
            print str(type(e)) + ": " + str(e) + ": reconnect"
            self.connect_to_hub()
            self.create_request_and_send(request_url, message, http_headers, message_id)
        except timeout, e:
            print str(type(e)) + ": " + str(e)
            self.handle_hub_error(message_id, httplib.REQUEST_TIMEOUT)               
        except error, e:
            # This exception is raised for socket-related errors. E.g
            # - the hub address is incorrect
            print str(type(e)) + ": " + str(e)
            self.handle_hub_error(message_id, None)
        except Exception, e:
            print str(type(e)) + ": " + str(e)
            self.handle_hub_error(message_id, None)
                                
    def send_message_to_hub(self, message, destination, message_id=None):
        ds = DSStub()
        hub_username = ds.get_hub_username()
        hub_password = ds.get_hub_password()
        destination_node_id = ds.get_node_id_by_node_name(destination)
        base64string = base64.encodestring(
                '%s:%s' % (hub_username, hub_password))[:-1]
        auth_header =  "Basic %s" % base64string
        http_headers = {
             'Authorization':auth_header,
             'Content-Type':'text/xml; charset=utf-8'
             }          
        request_url = "/node/" + destination_node_id
         
        self.send_http_request(request_url, message, http_headers, message_id)
        
    def do(self, data):
        dso = data
        ds = DSStub()
        http_request_dst = dso.get_dst(subject=dsconstants.SUBJECT_ROOT,
                    predicate=dsconstants.PREDICATE_HTTP_REQUEST)
        
        lock_dst = dso.get_dst(subject=dsconstants.SUBJECT_ROOT,
                    predicate=dsconstants.PREDICATE_LOCK)

        destination_dst = dso.get_dst(subject=dsconstants.SUBJECT_ROOT,
                    predicate=dsconstants.PREDICATE_MESSAGE_DESTINATION_NODE)
        destination = destination_dst.get_literal()
        
        request_id = None
        if http_request_dst != None and lock_dst != None:
            http_request = http_request_dst.get_literal()
            lock = lock_dst.get_literal()
            # generate request id
            request_id = hash(http_request)
            request_id_dst = DST(subject=dsconstants.SUBJECT_ROOT, 
                      predicate=dsconstants.PREDICATE_REQUEST_ID, 
                      literal=request_id)
            dso.add_dst(request_id_dst)                    
            # save response against request id
            ds.set_outstanding_requests(request_id, (http_request, lock))        

        # add request node_id
        node_id = ds.get_node_id()
        node_id_dst = DST(subject=dsconstants.SUBJECT_ROOT, 
                  predicate=dsconstants.PREDICATE_MESSAGE_REQUEST_NODE_ID, 
                  literal=node_id)
        dso.add_dst(node_id_dst)        
        
        # remove request, lock and destination from dso
        dso.remove_dst(dst=http_request_dst)
        dso.remove_dst(dst=lock_dst)
        dso.remove_dst(dst=destination_dst)
        
        # marshall dso
        xml_message = dso.to_xml()
        
        # send message to hub
        self.send_message_to_hub(xml_message, destination, request_id)